package com.me.study.concurrent.thread;

import com.me.common.util.ConcurrentUtil;
import com.me.common.util.SleepUtil;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/**
 * 多线程环境下，并发修改共享变量的测试   TODO 不知道测的啥玩意
 *
 * @author ME
 * @date   2019/3/27 17:47
 */
public class TestLock {

    /** 普通 volatile 共享变量，不保证原子性 */
    private static volatile Integer count1;
    /** 原子性的共享变量 */
    private static AtomicInteger count2;
    /**线程安全锁*/
    private static Lock lock = new ReentrantLock();

    @Test
    public void test0() {
        test(TestLock::changeFlag0);
        Assert.assertTrue("volatile 非原子性的，不相等", count1 != count2.get());
    }

    @Test
    public void test1() {
        test(TestLock::changeFlag1);
        Assert.assertTrue("相等", count1 == count2.get());
    }

    @Test
    public void test2() {
        test(TestLock::changeFlag2);
        Assert.assertTrue("相等", count1 == count2.get());
    }

    public static void test(Consumer<Integer> consumer) {
        int parties = 1000;
        CyclicBarrier barrier = new CyclicBarrier(parties);
        for (int i = 1; i <= parties; i++) {
            int finalI = i;
            new Thread(() -> {
                ConcurrentUtil.barrierAwait(barrier);
                consumer.accept(finalI);
            }, "Thread-" + i).start();
        }

        SleepUtil.seconds(1);
        System.out.println("Integer      : " + count1);
        System.out.println("AtomicInteger: " + count2);
    }

    public static void changeFlag0(Integer i) {
        count1 += i;
        count2.addAndGet(i);
    }

    public static void changeFlag1(Integer i) {
        lock.lock();
        try {
            count1 += i;
        } finally {
            lock.unlock();
        }
        count2.addAndGet(i);
    }

    public static void changeFlag2(Integer i) {
        synchronized (TestLock.class){
            count1 += i;
        }
        count2.addAndGet(i);
    }

    @Before
    public void before() {
        count1 = 0;
        count2 = new AtomicInteger(0);
    }

}
